classdef eyetrackexp < handle

    properties

        num_blocks = 8
        num_trials = 8
        FpCoccPE   = 330                % FpC = frames per cycle; 1 second @ 60Hz
        FpClearn   = [270,300,360,420]
        cycles     = 3                  % one cycle is there and back


        FpC
        Epar       = struct

        % Fixation

        fix        = struct('x',-0.5, ... % fraction of scn/2 size (for size of SPEM path)
            'y',0, ...
            'step',-0.5,...       % fixation cross and stimulus start to be step-ramp
            'time',2)           % ms

        % Display

        scn        = struct('mon',1, ... % 0 or 1
            'res',6, ...
            'w',1600, ... % 1 = 640x480; 3 = 1024x768
            'h',1200, ...
            'refresh_rate',60, ...% Refresh rate in Hz
            'wcm',40.8) 

        % stimulus

        stim       = struct('size',15,'rgb',[1 1 1])

        monitor    = 41    % width of monitor in cm
        view_dist  = 60    % distance from monitor in cm

        % Buffers

        blank      = 1
        ready      = 2
        fixation   = 3
        pause_str  = 4
        

        % keyboard

        ESC        = 52

        % Things I dont understand

        stim_speed_occPE
        stim_maxspeed_occPE

        stim_speed_learn
        stim_maxspeed_learn

        % Occluder

        occluder
        jump

        refresh_rate = 60
        resample = 1000

        vecMatrix

        Eyelink = 0 %turned off
        
        date
        
        phase_jump = 1/30
        
        % Every pauses trials there would be pause
        pauses = 32
        %
        trialtypes
        %
        randomFixation
        
        stimuli = inline('cos(x)','x')
        
    end

    methods

        function self = eyetrackexp()
            % New seed for random
            rand('twister',sum(100*clock));

        end

        function runExperiment(self)
            % This functions puts together all the preprocessing.
            self.prepareTrialTypes()
            self.prepareOccluder()
            self.prepareSpeeds()
            self.prepareJump()
            self.runScreen()
        end

        function prepareEyelink(self)

            %% EYETRACKER SETUP

            DriftCor = [4, 7, 10, 13, 16];  % blocks before which drift correction should occur

            if Eyelink('Initialize') ~= 0
                return
            end

            Eyelink('Message','Trigger %d',n);    % send a numbered message to the file

            Eyelink('Command', 'screen_phys_coords = -204, 152, 204, -152', physx1, physy1, physx2, physy2);
            % in millimetres
            Eyelink('Command', 'screen_pixel_coords = 1024 768 0 0', pixelx1, pixely1, pixelx2, pixely2);
            % screen resolution
            Eyelink('Command', 'screen_distance = 650 690', screendistance1, screendistance2);
            % to top and bottom NB top of screen should be eye height

            recording_parse_type = GAZE;
            saccade_velocity_threshold = 22;       % deg/s - 22 can detect 0.3deg saccades
            %                                         % 30 is >conservative
            saccade_acceleration_threshold = 3800; % deg/s/s - 3800 for small saccades
            %                                         % 8000 for reading.
            saccade_motion_threshold = 0.0;        % delays saccade onset until signif eye movt
            saccade_pursuit_fixup = 60;            % to detect saccades during pursuit,
            %                                         % velocity threshold raised
            fixation_update_interval = 0;

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

        end

        function prepareTrialTypes(self)
            % Creates matrix of trial types: 50% are '0' (will be occPE trials)
            % 12.5% each are '1,2,3,4' (will be different speeds in learning
            % trials)

            trialtype = zeros(self.num_blocks,self.num_trials);

            for b = 1:self.num_blocks
                trialtype(b,:) = randperm(self.num_trials);
                for t = 1:self.num_trials
                    if trialtype(b,t) > self.num_trials/2
                        trialtype(b,t) = 0;
                    end
                end
            end

            % creates matrix of target speeds, according to trial type
            % matrix above

            FpC = zeros(self.num_blocks,self.num_trials);
            for b = 1:self.num_blocks
                for t = 1:self.num_trials
                    if trialtype(b,t) == 0
                        FpC(b,t) = self.FpCoccPE;
                    else
                        FpC(b,t) = self.FpClearn(trialtype(b,t));
                    end
                end

            end
            % Save results
            self.FpC       = FpC;
        end

        function prepareOccluder(self)
            % Saves an instance of occluder in the property occluder
            self.occluder = occluder(self);
        end
        
        function prepareFixation(self)
            
            %self.randomFixation = round(rand(self.num_blocks,self.num_trials));
            %self.randomFixation(self.randomFixation == 0) = -1;
            %self.randomFixation = self.randomFixation * self.fix.x;
            self.randomFixation = ones(self.num_blocks,self.num_trials) * self.fix.x;
        end

        function prepareSpeeds(self)

            % average & maximum stimulus speeds in degrees/sec

            for n = 1:numel(self.FpClearn)
                self.stim_speed_learn(n) = 2*atand((0.5 * (self.monitor...
                    *-(self.fix.x))/self.view_dist)/((0.5*self.FpClearn(n))/60));
                self.stim_maxspeed_learn(n) = 60*atand(0.5*self.fix.x*cos(2*pi ...
                    *((0.25*self.FpClearn(n))+1)/self.FpClearn(n)) * ...
                    self.monitor/self.view_dist);
            end

        end

        function prepareJump(self)

            % The property jump is filled with an instance of the class
            % jump
            self.jump = jump(self);

        end

        function startDialog(self)

            prac = input('Practice? ','s');

            if prac == 'y';
                self.Epar.Sbj = 0; self.Epar.Exp = 0;

            else
                self.Epar.Sbj = input('Subject number? ');
                self.Epar.Exp = input('Experiment number? ');
            end

        end
        
        function runScreen(self)
            % Runs the screen. Because it depends on the GM in this case it
            % is only a place holder
        end
        
        function start_GM(self)
            % Initializes the graphical motor, i.e., cogent or PTB. It is
            % only a place holder.
        end
        
        function trialTransition(self)
            % Specifies the transition from one trial to other. In this
            % case it is a place holder.
        end
        
        function sendparameters(self,trial)
            [b,t] = ind2sub([self.num_blocks self.num_trials],trial);
            Eyelink('Message',['BLOCK ' num2str(b)]);
            Eyelink('Message',['TRIAL ' num2str(t)]);
            Eyelink('Message',['SUBJECT ' num2str(self.Epar.Sbj)]);
            Eyelink('Message',['EXPERIMENT ' num2str(self.Epar.Exp)]);
            Eyelink('Message',['FPC ' num2str(self.FpC(trial))]);
            Eyelink('Message',['OCC_X ' num2str(self.occluder.X(trial))]);
            Eyelink('Message',['OCC_W ' num2str(self.occluder.W(trial))]);
            Eyelink('Message',['JUMP_FRAME ' num2str(self.jump.frame(trial))]);
            Eyelink('Message',['JUMP_dPHASE ' num2str(self.jump.m(trial))]);
            Eyelink('Message',['FIXATION ' sprintf('%0.2f',self.randomFixation(trial))]);
        end

        function  v =  returnVectors(self,trial,resample)
            if nargin < 3; resample = self.resample; end
            % Resample is the resample in Hz
           
            v = nan(1,40000); %buffenr
            i = 1;
            c = 1;
            dt = 0;
            while dt <= self.cycles
                dt = dt+(self.refresh_rate/(resample*self.FpC(trial)));
                % When it is very close to the occludders center.
                if c && abs(dt - self.jump.frame(trial)) < 2/self.FpC(trial)
                    dt = dt + self.jump.m(trial);
                    c = 0;
                end
                v(i) = dt;
                i = i + 1;
            end
            v = self.randomFixation(trial)*(self.scn.w/2)*self.stimuli(v(~isnan(v))*2*pi)+self.scn.w/2;
        end

        function hp = plotTrial(self,trial)

            v = self.plotOccluder(trial);
            
            hp = plot((0:numel(v)-1),v,':r','linewidth',1);
            set(gca,'xticklabel',get(gca,'xtick')/self.resample);
            % Plot occluder
            set(gca,'ylim',[0,self.scn.w]);
            xlabel('Secs')
            ylabel('Pixel')

        end

        function plotSpeed(self,trial)
            v = self.returnVectors(trial);
            plot((0:numel(v)-1)/self.resample,gradient(v),'k','linewidth',1)
            % Plot occluder

            title(['Instantaneous speed: trial ' int2str(trial)])
            xlabel('Secs')
            ylabel('Pixel/Sec')

        end
        
        function [points00,points01,points10,points11] = occludersPosition(self,trial)
           % Returns the position of the occluders in radians/pi 
            left  = (self.occluder.X(trial) - self.occluder.W(trial)/2)...
                / (self.randomFixation(trial)*(self.scn.w/2));
            right = (self.occluder.X(trial) + self.occluder.W(trial)/2)...
                / (self.randomFixation(trial)*(self.scn.w/2));
            if left < -1; left  = -1; end
            if right > 1; right = 1; end
            
            v0 = real(acos(left))/pi;
            v1 = real(acos(right))/pi;
            % The two solution of acos;
            points00 = ((0:self.cycles-1) * 2 + v0);
            points01 = ((0:self.cycles-1) * 2 + (2 - v0));
            points10 = ((0:self.cycles-1) * 2 + v1);
            points11 = ((0:self.cycles-1) * 2 + (2 - v1));
            % Adds the jump, if there is any.
            points00(points00 > self.jump.frame(trial)*2) = points00(points00 > self.jump.frame(trial)*2) - self.jump.m(trial)*2;
            points01(points01 > self.jump.frame(trial)*2) = points01(points01 > self.jump.frame(trial)*2) - self.jump.m(trial)*2;
            points10(points10 > self.jump.frame(trial)*2) = points10(points10 > self.jump.frame(trial)*2) - self.jump.m(trial)*2;
            points11(points11 > self.jump.frame(trial)*2) = points11(points11 > self.jump.frame(trial)*2) - self.jump.m(trial)*2;
           
        end

        function [v] = plotOccluder(self,trial,y0,y1,ah)
            % Plots the occluder

            if nargin < 5; ah = gca; end
            
            v = self.returnVectors(trial);
            
            
            if nargin < 3; y0 = 0; end
            if nargin < 4; y1 = self.scn.w; end
            
            oRight = self.scn.w/2 + self.occluder.X(trial) + self.occluder.W(trial)/2;
            oLeft  = self.scn.w/2 + self.occluder.X(trial) - self.occluder.W(trial)/2;
            
            ov =  (v < max(oRight,oLeft)).* (v > min(oRight,oLeft));

            %dps = (self.resample*self.FpC(trial))/(self.scn.refresh_rate*2);
            
            if ov(1) == 1; 
                points0 = 1; 
                points1 = 1; 
                t = 1;
            else
                points0 = [];
                points1 = [];
                t = 0;
            end
            for i = 2:numel(ov)
                if ov(i) && t
                    points1(end) = points1(end) + 1;
                elseif ov(i) && ~t
                    points0(end+1) = i;
                    points1(end+1) = i;
                    t = 1;
                elseif ~ov(i) && t
                    t = 0;
                end
            end
            patch([points0;points1;points1;points0],...
                kron(ones(1,numel(points0)),[y0,y0,y1,y1]'),[0.9,0.9,0.9],...
                'edgecolor',[0.9,0.9,0.9]); hold on
        end
        
        function saveLog(self,name)
           if nargin < 2; 
               name =['eyetrack.exp.' datestr(now,'mm_HH_DD_MM_YY') '.mat'];
           end
           self.date = now;
           save(name,'self')
        end

    end
end